大家早安!今天是完賽日囉(握拳)
最近都一直下雨,先來一張re-frame文件裡美美的圖
就像是水循環一樣,水經過了evaporation蒸發變成水蒸氣、經過凝結、gravity重力降下雨與雪、也經過convection對流而循環不已。我們可以把前端世界的的data flow資料流也做一樣的想像。
re-frame這個框架是屬於event driven design,會透過event來趨動data走完六個階段的生命週期。這些event就像骨牌一樣,第一個階段完成後,產生第二個階段,第二個階段完成產生下個階段...。最終在前端SPA頁面快速反饋網站使用者需要的功能與效果。
we hang functions on the (data) loop at various points to compute the data's phase changes. Ref
既然是框架,必定有很完整的架構,讓我們在撰寫前端cljs程式時,可以把資料流不同階段的function拆分,掛在data loop的相對應的階段。
以下就要介紹在re-frame前端框架裡的data loop六階段:
想知道從
Event Dispatch
,Event Handling
Effect Handling
Query
到subscribe state產生view
到前端的DOM這一系列的code怎麼實作出來的思考脈絡,可以看文件這篇詳細的說明。
當事件被trigger的時候,是data loop循環的第一個階段Event Dispatch
:
eg.
這個第一階段最重要,如果沒有第一個階段,後面其他五個階段也不會產生啦。
使用者刪除某個按鈕的event function,可能長得像這樣:
(defn delete-button
[item-id]
[:div.garbage-bin
:on-click #(re-frame.core/dispatch [:delete-item item-id])])
為了回應event,app就要決定下一步action是什麼
在Event handling階段的函式會去做計算這個event會改變了外部的哪些值(side-effect)
More accurately, event handler functions compute a declarative description of the required side effects - represented as data.
eg. 使用者按下刪除按鈕,會去db 裡刪資料
(defn handler
[coeffects event]
(let [item-id (second event) ;; extract id from event vector
db (:db coeffects) ;; extract the current application state
new-db (dissoc-in db [:items item-id])] ;; new app state
{:db new-db})) ;; a map of the necessary effects
;; `new-db` is the newly computed application state
以上的語法可以用destructing(解構)再寫的更簡潔一些
(defn handler
[{:keys [db]} [_ item-id]] ;; <--- new: obtain db and item-id directly
{:db (dissoc-in db [:items item-id])}) ;;
side effect會改變外部的值
這句聽起來很嚇人,但是如果沒有side effect的話。網頁停在那裡,不做任何動作,不改變任何state,也不更新任何值,不是很無用的嗎XD。
上個階段Event handling階段計算好的side effect,在這個階段會執行。
這個階段改變app-state
( or app-db),就會triger第4到6的階段。
在re-frame程式裡,會把Day27介紹的reagent/atom binding到app-db
。
語法:
(def app-db (reagent/atom {})) ;; a Reagent atom, containing a map
atom的概念雖然是透過reference方式來管理狀態變更,但我們可以想像成app-db
、或in-memory db
,這樣就更為直接啦~
re-frame的機制因為有了effect handling階段,會讓side effect更容易被monitor和debug。
re-frame app可以用reg-fx
註冊effects handlers,例如這樣:
(re-frame.core/reg-fx ;; part of the re-frame API
:db ;; the effects key
(fn [val] ;; the handler function for the effect
(reset! app-db val))) ;; put the new value into the ratom app-db
但在實務上不需要去註冊app-db這個步驟
我們反而是需要寫自己的handler然後註冊,
例如註冊一個刪除事件
(re-frame.core/reg-event-fx ;; a part of the re-frame API
:delete-item ;; the kind of event
handler) ;; the handler function for this kind of event
由於re-frame是奠基在react框架上,因此關於state與lifecycle也可以參考react官方文件 - State and Lifecycle的說明。
State is similar to
props
, but it is private and fully controlled by the component.
re-frame 從event dispatch到操作reagent / react DOM圖解說明
re-frame第四到第六的階段用以下方程式來表達
v = f(s)
;; view = function(state)
當state改變時,function會重新運算,render對應的view
上個階段update完app-db之後,會trigger第四階段的query把app state
的資料拿出來。帶入ViewFunction
(v = f(s)公式裡的f
),快速地運算出前端需要render的view。
(defn query-fn
[db v] ;; db is the current value in app-db, v the query vector
(:items db)) ;; not much of a materialised view
(re-frame.core/reg-sub ;; part of the re-frame API
:query-items ;; query id
query-fn) ;; function to perform the query
這個階段的ViewFunction code可能長得像這樣子:
(defn items-view
[]
(let [items (subscribe [:query-items])] ;; source items from app state
[:div (map item-render @items)])) ;; assume item-render already written
其中的 (subscribe [:query-items])
就會在上個階段call query-n
多個ViewFunction
組合起來就會是component啦~我們在第25天有實做過幾個簡單的Reagent component。
這些ViewFunction
會運算並回傳24天介紹的hiccup (一種用cljs語法表達的前端html格式) 再往下一階段(DOM
)傳。
Document Object Model,文件物件模型,把HTML文件內的各個標籤,包括文字、圖片等等都定義成物件
為了要render出正確的DOM
,re-frame使用的機制是subscribe
來訂閱最新的state,確保UI可以一直更新並即時顯示。
當hiccup格式轉為DOM,這裡就是已經是react及reagent(用cljs寫react)的範疇啦~
從以上的說明,是不是對re-frame是怎麼再把reagent包一層的概念有更多的認識呢?
很榮幸參與今年2022年的鐵人賽,這三十天內除了鐵人賽也跟前兩次(2018, 2020)一樣在鐵人賽的過程中去跑了馬拉松,很幸運地兩者都有兼顧順利完賽。而且跟前兩次經驗不一樣的是,今年寫鐵人賽時的身份已經是工程師啦!
之前也蠻懷疑全職的時候有辦法連續撐30天、每天下班後都打起精神再持續花2-4小時學習新東西嗎?事實證明,人的潛力是無窮的。 "Whether you think you can, or you think you can't, you're right" 。相信自己的毅力一定可以做得到,那就做得到!百分之百的commitment,一點點的事前規劃和策略運用、加上一點點時間管理大師
的能力。完賽並不是遙不可及的事。(假日也是有抽空去旅行、跑步、爬山等,只是出外有時要記得帶電腦去飯店趕稿。:P)
第一次寫Functional programming相關系列文,在構思及撰寫過程有很多不完美的地方,在寫的時候力求初淺理解但仍然有誤解之處,會再抽空反覆回去修改文章內容。十分感謝公司前輩的指點及參與討論~
謝謝推坑我組團的龍哥,謝謝自主學習的團員們互相激勵,謝謝在30天為我加油打氣的好同事們、Bater、親友們和黃色小鴨鴨(XD) Love you all。
培養新的習慣需要21天,經過30天的洗禮之後。每天都會早起研究程式,很喜歡這種morning routine,是種很充實、很踏實的感覺。
但這30天只是functional programming學習的起點~希望自己能再更熟悉FP思維,會繼續深化研究下去,活用Clojure語法來解Kata。
鐵人賽我們下次見!
恭喜完賽!哇~又一次克服了連續三十天的挑戰,而且是全新主題,同時間還有正職工作與馬拉松,真的是很不簡單。
我相信這種克服挑戰的精神與能力會是未來面對困境珍貴的技能,可以屢屢化險為夷,然後得到更多成長與體驗。
很榮幸能成為見證這個艱難的道路上的支持者與鼓勵者,不論之後會遇到什麼樣的新課題,也會陪伴你一起克服的唷!
謝謝Bater在我成為工程師的路上一直不斷鼓勵我,未來也要一起在專業領域上精進加油唷!